package com.example.sticker;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PathEffect;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class StickerView extends SurfaceView implements SurfaceHolder.Callback {
	private static final int TRANSLATE = 0;
	private static final int SCALE = 1;
	private static final int ROTATE = 2;
	private int mCloseWidth;
	private int mCloseHeight;
	private int mCircleWidth;
	private int mCircleHeight;
	
	private Bitmap mCloseBitmap;
	private Bitmap mCircleBitmap;
	private Bitmap mStickerImage;
	
	private int mStickerImageW;
	private int mStickerImageH;
	
	private Matrix mStickerMatrix;
	private Matrix mMatrix;
	
	private Context mContext;
	private SurfaceHolder mSurfaceHolder = null;
	
	private float mInitHeight;
	private float mInitWidth;
	
	private PaintFlagsDrawFilter mDrawFilter = new PaintFlagsDrawFilter(0, 3);
	
	private Paint mLinePaint;
	
	private float mInitDegrees;
	
	private float mHorizontal = 0.0F;
	private float mVertical = 0.0F;
	
	private float mDiagonalLen;
	
	private float mRotatableDegree = 3.0F;
	
	private OnImageCloseListener mListener;
	
	private int mCurOper = -1;//j
	private int mCurCtrl = -1;//q
	
	private float[] mPositions;
	private float[] mPrevPositions;
	private float[] mCurPositions;
	
	private RectF mRectF;
	private RectF mPrevRectF;
	private RectF mCurRectF;
	
	private Point mPoint;
	private Point mPrevPoint;
	private Point mCurPoint;
	
	private Paint mBitmapPaint;
	private PathEffect mDashEffect;
	
	private float mScaleX = 1.0F;
	private float mScaleY = 1.0F;
	
	private float mStartDegree;
	private float mEndDegree;
	
	private float mBgHeight;
	private float mBgWidth;

	public StickerView(Context context, AttributeSet attrs) {
		super(context, attrs);		
		
		mStickerMatrix = new Matrix();
		mMatrix = new Matrix();
		
		initSurface(context);
	}
	
	private void initSurface(Context context) {
		mContext = context;
		mSurfaceHolder = getHolder();
		setZOrderOnTop(true);
		mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
		mSurfaceHolder.addCallback(this);
		setFocusable(true);
	}
	
	public void setStickerBitmap(Bitmap bitmap) {
		mStickerImage = bitmap;
		if (mStickerImage == null) {
			return;
		}
		
		mStickerImageW = mStickerImage.getWidth();
		mStickerImageH = mStickerImage.getHeight();
		
		mInitDegrees = (float) getDegree(mStickerImageW, mStickerImageH);
		float[] temp = new float[10];
		//0.1------------2.3
		// |			  |
		// |	  8.9	  |
		// |			  |
		//6.7------------4.5
		temp[0] = 0;
		temp[1] = 0;
		temp[2] = mStickerImageW;
		temp[3] = 0;		
		temp[4] = mStickerImageW;
		temp[5] = mStickerImageH;
		temp[6] = 0;
		temp[7] = mStickerImageH;
		temp[8] = mStickerImageW / 2;
		temp[9] = mStickerImageH / 2;
		
		mPositions = temp;
		mPrevPositions = mPositions.clone();
		mCurPositions = mPositions.clone();
		
		mRectF = new RectF(0, 0, mStickerImageW, mStickerImageH);
		mPrevRectF = new RectF();
		mCurRectF = new RectF();
		
		mPoint = new Point(mStickerImageW / 2, mStickerImageH / 2);
		mPrevPoint = new Point(mStickerImageW / 2, mStickerImageH / 2);
		mCurPoint = new Point(0, 0);
		
		mBitmapPaint = new Paint();
		mBitmapPaint.setAntiAlias(true);
		mBitmapPaint.setFilterBitmap(true);

		mLinePaint = new Paint();
		mLinePaint.setColor(getResources().getColor(R.color.white));
		mLinePaint.setAntiAlias(true);
		mLinePaint.setFilterBitmap(true);
		mLinePaint.setStrokeWidth(Util.dpToPixel(2.0F));
		mLinePaint.setStyle(Paint.Style.STROKE);
		
		float[] dash = new float[2];
		dash[0] = Util.dpToPixel(4.0F);
		dash[1] = Util.dpToPixel(5.0F);
		mDashEffect = new DashPathEffect(dash, 0.0F);
		
		mStartDegree = 0;
		mEndDegree = 0;		
		updateMatrix(SCALE);
		
		float height = getDistance(mPrevPositions[0], mPrevPositions[1], mPrevPositions[6], mPrevPositions[7]);
		float width = getDistance(mPrevPositions[0], mPrevPositions[1], mPrevPositions[2], mPrevPositions[3]);
	    if (getWidth() > width) {
	    	mHorizontal = (getWidth() / 2 - mPrevPositions[8]);
	    }
	    
	    if (getHeight() > height) {
	    	mVertical = (getHeight() / 2 - mPrevPositions[9]);
	    }
	    
	    if ((mHorizontal != 0.0F) || (mVertical != 0.0F)) {
	    	updateMatrix(TRANSLATE);
	    }
	    
	    if (mInitHeight > Math.min(height, width)) {
	    	mInitHeight = Math.min(height, width);
	    }
	    
		mDiagonalLen = ((float) Math.sqrt(Math.pow(mInitHeight, 2.0D)
				+ Math.pow(
						Math.max(mStickerImageW, mStickerImageH)
								* (mInitHeight / Math.min(mStickerImageW, mStickerImageH)), 2.0D)));
		drawView();
	}
	
	public void setCloseListener(OnImageCloseListener listener) {
		mListener = listener;
	}

	private void updateMatrix(int type) {
		switch (type) {
		case TRANSLATE:
			mStickerMatrix.postTranslate(mHorizontal, mVertical);
			mMatrix.postTranslate(mHorizontal, mVertical);
			break;
			
		case SCALE:
			mStickerMatrix.postScale(mScaleX, mScaleX, mPrevPositions[8], mPrevPositions[9]);
			mMatrix.postScale(mScaleY, mScaleY, mPrevPositions[8], mPrevPositions[9]);
			break;
			
		case ROTATE:
			mStickerMatrix.postRotate(mEndDegree - mStartDegree, mPrevPositions[8], mPrevPositions[9]);
			mMatrix.postRotate(mEndDegree - mStartDegree, mPrevPositions[8], mPrevPositions[9]);
			break;
			
		default:
			break;
		}
		
		mStickerMatrix.mapPoints(mPrevPositions, mPositions);
		mStickerMatrix.mapRect(mPrevRectF, mRectF);
		mMatrix.mapPoints(mCurPositions, mPositions);
		mMatrix.mapRect(mCurRectF, mRectF);
	}
	
	private void drawView() {
		Canvas canvas = mSurfaceHolder.lockCanvas();
		if (canvas == null || mCurPositions == null) {
			mSurfaceHolder.unlockCanvasAndPost(canvas);
			return;
		}
		
		canvas.drawColor(0, PorterDuff.Mode.CLEAR);
		if (mStickerImage != null) {
			canvas.setDrawFilter(mDrawFilter);
			canvas.drawBitmap(mStickerImage, mStickerMatrix, null);
			canvas.drawLine(mCurPositions[0], mCurPositions[1], mCurPositions[2], mCurPositions[3], mLinePaint);
			canvas.drawLine(mCurPositions[2], mCurPositions[3], mCurPositions[4], mCurPositions[5], mLinePaint);
			canvas.drawLine(mCurPositions[4], mCurPositions[5], mCurPositions[6], mCurPositions[7], mLinePaint);
			canvas.drawLine(mCurPositions[6], mCurPositions[7], mCurPositions[0], mCurPositions[1], mLinePaint);
			
			for (int i = 0; i < mCurPositions.length - 2;) {
				if (i == 0) {
					canvas.drawBitmap(mCloseBitmap, mCurPositions[i] - mCloseWidth / 2, mCurPositions[(i + 1)] - mCloseHeight / 2, mBitmapPaint);
				} else {
					canvas.drawBitmap(mCircleBitmap, mCurPositions[i] - mCircleWidth / 2, mCurPositions[(i + 1)] - mCircleHeight / 2, mBitmapPaint);
				}
				i += 2;
			}
		}
		
		mSurfaceHolder.unlockCanvasAndPost(canvas);
	}

	public void init(float height, float width) {
		if (height > 0) {
			mBgHeight = height;
		}
		
		if (width > 0) {
			mBgWidth = width;
		}
		
		if (checkIsValid()) {
			drawStickerImageView();
		}
		
	}
	
	private boolean checkIsValid() {
		if ((mBgHeight <= 0.0F) || (mBgWidth <= 0.0F) || (getHeight() <= 0) || (getWidth() <= 0)) {
			return false;
		}
		
		float x = (getWidth() - mBgWidth) / 2;
		float y = (getHeight() - mBgHeight) / 2;
		RectF rectF = new RectF(x, y, x + mBgWidth, y + mBgHeight);
		if (!rectF.intersect(mPrevRectF)) {
			return true;
		}
		if (mRectF == null) {
			return true;
		}
		
		return false;
	}
	
	private void drawStickerImageView() {
		mStickerImage = null;
		if (mListener != null) {
			mListener.closeStickerImage();
		}
		drawView();
	}
	
	private static double getDegree(double width, double height) {
		return 180.0D * Math.asin(width / Math.sqrt(width * width + height * height)) / Math.PI;
	}
	
	private static float getDistance(float x, float y, float x1, float y1) {
		return (float) Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mCloseBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_sticker_close);
		mCircleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_sticker_control);
		
		mCloseWidth = mCloseBitmap.getWidth();
		mCloseHeight = mCloseBitmap.getHeight();
		
		mCircleWidth = mCircleBitmap.getWidth();
		mCircleHeight = mCircleBitmap.getHeight();
		
		mInitHeight = Util.dpToPixel(60.0F);
		mInitWidth = Util.dpToPixel(160.0F);
		
		drawView();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {}	

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		if (mStickerImage == null) {
			return super.dispatchTouchEvent(event);
		}
		int currentX = (int) event.getX();
		int currentY = (int) event.getY();
		int curOper = mCurOper;//v1, j
		
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			int padding = Util.dpToPixel(5.0F);
			Rect rect = new Rect(currentX - mCircleWidth / 2 - padding, currentY - mCircleHeight / 2 - padding,
					padding + currentX + mCircleWidth / 2, padding + currentY + mCircleHeight / 2);
			int i = 0;
			int j = 0;

			while (j < mCurPositions.length) {
				if (rect.contains((int) mCurPositions[j], (int) mCurPositions[j + 1])) {
					break;
				}

				i++;
				j += 2;
			}
			
			if (j >= mCurPositions.length) {
				mCurCtrl = -1;
			} else {
				mCurCtrl = i;
			}

			if (mCurCtrl == -1) {				
				if (mCurRectF.contains(currentX, currentY)) {
					curOper = 1;
				} else {
					curOper = 0;							
				}
				
				if (curOper == 0) {
					curOper = -1;
				} else {
					curOper = 7;
				}
			} else {
				curOper = 7;
			}
			
			if (mCurCtrl == 0) {
				curOper = 5;
			}

			mCurPoint.x = currentX;
			mCurPoint.y = currentY;
			mCurOper = curOper;
			if (curOper == 5) {
				drawStickerImageView();
			}
			break;
			
		case MotionEvent.ACTION_MOVE:
			if (0 <= mCurCtrl && mCurCtrl < 4) {
				switch (mCurCtrl) {
				case 1:
					curOper = 6;
					break;
					
				case 2:
					curOper = 3;
					break;
					
				case 3:
					curOper = 4;
					break;
					
				default:
					break;
				}
			} else {
				if (mCurOper == 7) {
					curOper = 0;
				}
			}
			Log.i("xdebug", new StringBuilder("curOper is ").append(curOper).append(", mCuroper is ").append(mCurOper).toString());
			switch (curOper) {
			case 0:
				mPoint.x += currentX - mCurPoint.x;
				mPoint.y += currentY - mCurPoint.y;
				
				mHorizontal = mPoint.x - mPrevPoint.x;
				mVertical = mPoint.y - mPrevPoint.y;
				
				mPrevPoint.x = mPoint.x;
				mPrevPoint.y = mPoint.y;
				updateMatrix(TRANSLATE);
				break;
				
			case 1:
			case 2:
				break;
				
			case 6:
			case 4:
			case 3:
				int idx = mCurCtrl * 2;
				float prevPositionX = mPrevPositions[idx];
				float prevPositionY = mPrevPositions[idx + 1];
				float curPositionX = mCurPositions[idx];
				float curPositionY = mCurPositions[idx + 1];
				float curX = event.getX();
				float curY = event.getY();
				float diagonalLen = 0;
				float dis = 0;
				
				if (mCurCtrl >= 0 && mCurCtrl < 2) {
					prevPositionX = mPrevPositions[idx + 4];//v5
					prevPositionY = mPrevPositions[idx + 5];//v4
					curPositionX = mCurPositions[idx + 4];//v3
					curPositionY = mCurPositions[idx + 5];//v2
				} else if (mCurCtrl >= 2) {
					prevPositionX = mPrevPositions[idx - 4];//v5
					prevPositionY = mPrevPositions[idx - 3];//v4
					curPositionX = mCurPositions[idx - 4];//v3
					curPositionY = mCurPositions[idx - 3];//v2
				}
				float oriDiagonalLen = getDistance(mPrevPositions[idx], mPrevPositions[idx + 1], prevPositionX, prevPositionY);
				float newDiagonalLen = getDistance(curX, curY, prevPositionX, prevPositionY);
				float disCurCur = getDistance(mCurPositions[idx], mCurPositions[idx + 1], curPositionX, curPositionY);
				float v2 = getDistance(curX, curY, curPositionX, curPositionY);
				if (v2 >= mDiagonalLen) {
					mLinePaint.setPathEffect(null);
					diagonalLen = v2;
				} else {
					diagonalLen = mDiagonalLen;
					mLinePaint.setPathEffect(mDashEffect);					
				}
				
				if (newDiagonalLen > 10.0F) {
					dis = newDiagonalLen;
				} else {
					dis = 10.0F;
				}
				
				mScaleX = dis / oriDiagonalLen;//v2 / v8;
				mScaleY = diagonalLen / disCurCur;
				
				updateMatrix(SCALE);
				Point point1 = new Point((int) event.getX(), (int) event.getY());
				Point point2 = new Point((int) mPrevPositions[8], (int) mPrevPositions[9]);
				
				int xDiff = point1.x - point2.x;
				int yDiff = point1.y - point2.y;
				Log.d("xdebug", "xDiff : " + xDiff + ", yDiff : " + yDiff);
				float degree = (float) getDegree(xDiff, yDiff);
				if (!Float.isNaN(degree)) {
					if (xDiff <= 0 && yDiff <= 0) {
						
					} else if (xDiff <= 0 && yDiff >= 0) {
						mEndDegree = -180.0F - degree;
					} else if (xDiff >= 0 && yDiff >=0) {
						mEndDegree = 180.0F - degree;
					} else if (xDiff < 0 || yDiff < 0) {
						mEndDegree = degree;
					}
				} else {
					mEndDegree = 0;
				}
				switch (mCurCtrl) {
				case 1:
					mEndDegree -= mInitDegrees;
					break;
				
				case 2:
					mEndDegree -= 180.0F - mInitDegrees;
					break;
					
				case 3:
					mEndDegree -= 180.0F + mInitDegrees;
					break;
					
				default:					
					break;
				}
				updateDegree();
				if (90 - mRotatableDegree < mEndDegree && mEndDegree < 90 + mRotatableDegree) {
					mEndDegree = 90.0F;
				} else if (180 - mRotatableDegree < mEndDegree && mEndDegree < 180 + mRotatableDegree) {
					mEndDegree = 180.0F;
				} else if (270 - mRotatableDegree < mEndDegree && mEndDegree < 270 + mRotatableDegree) {
					mEndDegree = 270.0F;
				} else if (mEndDegree < mRotatableDegree || mEndDegree > 360 - mRotatableDegree) {
					mEndDegree = 0;
				}
				updateMatrix(ROTATE);
				mStartDegree = mEndDegree;
				break;
				
			case 5:
				drawStickerImageView();
				break;
				
			default:
				break;
			}
			
			mCurPoint.x = currentX;
			mCurPoint.y = currentY;
			mCurOper = curOper;
			drawView();
			break;
			
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			curOper = -1;
			if (mCurCtrl == 0 || checkIsValid()) {
				curOper = 5;
			}
			mCurCtrl = -1;
			break;
			
		default:
			break;
		}
		
		return true;
	}
	
	private void updateDegree() {
		while (mEndDegree < 0) {
			mEndDegree += 360.0F;
		}
	}
}